/**
 * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
 */

package net.sourceforge.pmd.eclipse.util;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.Date;
import java.util.List;
import java.util.Map;

import org.apache.commons.lang3.StringUtils;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.RGB;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.graphics.Region;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Event;
import org.eclipse.swt.widgets.Listener;
import org.eclipse.swt.widgets.Tree;
import org.eclipse.swt.widgets.TreeItem;

import net.sourceforge.pmd.Rule;
import net.sourceforge.pmd.eclipse.ui.Shape;
import net.sourceforge.pmd.eclipse.ui.ShapePainter;
import net.sourceforge.pmd.eclipse.ui.preferences.br.CellPainterBuilder;
import net.sourceforge.pmd.eclipse.ui.preferences.br.RuleFieldAccessor;
import net.sourceforge.pmd.util.ClassUtil;

public final class Util {

    public static final Object[] EMPTY_ARRAY = new Object[0];

    public static final Comparator<Method> METHOD_NAME_COMPARATOR = new Comparator<Method>() {
        @Override
        public int compare(Method a, Method b) {
            return a.getName().compareTo(b.getName());
        }
    };

    public static final Comparator<String> COMP_STR = new Comparator<String>() {
        @Override
        public int compare(String a, String b) {
            return a.compareTo(b);
        }
    };
    public static final Comparator<Integer> COMP_INT = new Comparator<Integer>() {
        @Override
        public int compare(Integer a, Integer b) {
            return a.compareTo(b);
        }
    };
    public static final Comparator<Long> COMP_LONG = new Comparator<Long>() {
        @Override
        public int compare(Long a, Long b) {
            return a.compareTo(b);
        }
    };
    public static final Comparator<Float> COMP_FLOAT = new Comparator<Float>() {
        @Override
        public int compare(Float a, Float b) {
            return a.compareTo(b);
        }
    };
    public static final Comparator<Boolean> COMP_BOOL = new Comparator<Boolean>() {
        @Override
        public int compare(Boolean a, Boolean b) {
            return a.compareTo(b);
        }
    };
    public static final Comparator<Date> COMP_DATE = new Comparator<Date>() {
        @Override
        public int compare(Date a, Date b) {
            return a.compareTo(b);
        }
    };
    public static final Comparator<Character> COMP_CHR = new Comparator<Character>() {
        @Override
        public int compare(Character a, Character b) {
            return a.compareTo(b);
        }
    };

    private Util() {
    }

    /**
     * Scans the source for occurrences of the prefix char and assumes that all letters that follow immediately after
     * make up a reference name. Returns the position and lengths of all names found.
     * 
     * NOTE: Doesn't handle the prefix within escaped or quoted strings
     * 
     * @param source
     * @param prefix
     * @return List<int[]>
     */
    public static List<int[]> referencedNamePositionsIn(String source, char prefix) {
        List<int[]> namePositions = new ArrayList<>();
        if (StringUtils.isBlank(source)) {
            return namePositions;
        }

        int pos = source.indexOf(prefix);
        int max = source.length();

        while (pos >= 0) {
            int end = pos + 1;
            while (end < max && Character.isLetter(source.charAt(end))) {
                end++;
            }
            int length = end - pos - 1;
            if (length < 1) {
                pos = source.indexOf(prefix, pos + 1);
                continue;
            }

            namePositions.add(new int[] { pos + 1, length });
            pos = source.indexOf(prefix, pos + length);
        }

        return namePositions;
    }

    /**
     * Extract and return all the string fragments listed in the list of positions where each position = int
     * [start][length]
     * 
     * @param source
     * @param positions
     * @return List<String>
     */
    public static List<String> fragmentsWithin(String source, List<int[]> positions) {
        List<String> fragments = new ArrayList<>(positions.size());
        for (int[] position : positions) {
            fragments.add(source.substring(position[0], position[0] + position[1]));
        }
        return fragments;
    }

    public static String signatureFor(Method method, String[] unwantedPrefixes) {
        StringBuilder sb = new StringBuilder();
        Class<?> returnType = method.getReturnType();
        if ("void".equals(returnType.getName())) { // TODO is there a better way?
            sb.append("void ");
        } else {
            signatureFor(returnType, unwantedPrefixes, sb);
            sb.append(' ');
        }
        Class<?>[] types = method.getParameterTypes();
        if (types.length == 0) {
            sb.append(method.getName());
            sb.append("()");
            return sb.toString();
        }

        sb.append(method.getName()).append('(');
        signatureFor(types[0], unwantedPrefixes, sb);
        for (int i = 1; i < types.length; i++) {
            sb.append(',');
            signatureFor(types[i], unwantedPrefixes, sb);
        }
        sb.append(')');
        return sb.toString();
    }

    private static String filteredPrefixFrom(String paramType, String[] unwantedPrefixes) {
        for (String prefix : unwantedPrefixes) {
            if (paramType.startsWith(prefix)) {
                return paramType.substring(prefix.length());
            }
        }
        return paramType;
    }

    public static String signatureFor(Class<?> type, String[] unwantedPrefixes) {
        StringBuilder sb = new StringBuilder();
        signatureFor(type, unwantedPrefixes, sb);
        return sb.toString();
    }

    private static void signatureFor(Class<?> type, String[] unwantedPrefixes, StringBuilder sb) {
        String typeName = ClassUtil.asShortestName(type.isArray() ? type.getComponentType() : type);
        typeName = filteredPrefixFrom(typeName, unwantedPrefixes);

        sb.append(typeName);
        if (type.isArray()) {
            sb.append("[]");
        }
    }

    public static Comparator<Rule> comparatorFrom(final RuleFieldAccessor accessor, final boolean inverted) {
        if (accessor == null) {
            throw new IllegalArgumentException("Accessor is required");
        }

        return new Comparator<Rule>() {
            @Override
            public int compare(Rule a, Rule b) {
                Comparable ca = accessor.valueFor(a);
                Comparable cb = accessor.valueFor(b);

                int result = (ca == null) ? -1 : (cb == null) ? 1 : ca.compareTo(cb);

                return inverted ? result * -1 : result;
            }
        };
    }

    public static void removeListeners(Control widget, int listenerType) {
        for (Listener listener : widget.getListeners(listenerType)) {
            widget.removeListener(listenerType, listener);
        }
    }

    public static int indexOf(Object[] items, Object choice) {
        for (int i = 0; i < items.length; i++) {
            if (items[i].equals(choice)) {
                return i;
            }
        }
        return -1;
    }

    /**
     * Method asCleanString.
     * 
     * @param original
     *            String
     * @return String
     */
    public static String asCleanString(String original) {
        return original == null ? "" : original.trim();
    }

    public static CellPainterBuilder itemAsShapeFor(final int width, final int height, final Shape shapeId,
            final int horizAlignment, final Map<Object, RGB> coloursByItem) {

        return new AbstractCellPainterBuilder() {
            private Color getterColorIn(TreeItem tItem, RuleFieldAccessor getter) {

                Object value = valueFor(tItem, getter);

                RGB color = coloursByItem.get(value);
                return color == null ? null : colorManager().colourFor(color);
            }

            @Override
            public void addPainterFor(final Tree tree, final int columnIndex, final RuleFieldAccessor getter,
                    Map<Integer, List<Listener>> listenersByEventCode) {

                final int xBoundary = 3;

                Listener paintListener = new Listener() {
                    @Override
                    public void handleEvent(Event event) {
                        if (event.index != columnIndex) {
                            return;
                        }

                        Color clr = getterColorIn((TreeItem) event.item, getter);
                        if (clr == null) {
                            return;
                        }

                        Color original = event.gc.getBackground();

                        event.gc.setBackground(clr);

                        int xOffset = 0;
                        final int cellWidth = widthOf(columnIndex, tree);

                        switch (horizAlignment) {
                        case SWT.CENTER:
                            xOffset = cellWidth / 2 - width / 2 - xBoundary;
                            break;
                        case SWT.RIGHT:
                            xOffset = cellWidth - width - xBoundary;
                            break;
                        case SWT.LEFT:
                            xOffset = 0;
                            break;
                        default:
                            // ignored
                        }

                        ShapePainter.drawShape(width, height, shapeId, event.gc, event.x + xOffset, event.y, null);

                        event.gc.setBackground(original);
                    }

                };

                Listener measureListener = new Listener() {
                    @Override
                    public void handleEvent(Event e) {
                        if (e.index != columnIndex) {
                            return;
                        }
                        e.width = width;
                        e.height = height;
                    }
                };

                addListener(tree, SWT.PaintItem, paintListener, listenersByEventCode);
                addListener(tree, SWT.MeasureItem, measureListener, listenersByEventCode);
            }
        };
    }

    public static void addListener(Control control, int eventType, Listener listener,
            Map<Integer, List<Listener>> listenersByEventCode) {

        Integer eventCode = Integer.valueOf(eventType);

        control.addListener(eventType, listener);
        if (!listenersByEventCode.containsKey(eventCode)) {
            listenersByEventCode.put(eventCode, new ArrayList<Listener>());
        }

        listenersByEventCode.get(eventCode).add(listener);
    }

    public static CellPainterBuilder backgroundBuilderFor(final int systemColourIndex) {

        return new CellPainterBuilder() {
            @Override
            public void addPainterFor(final Tree tree, final int columnIndex, final RuleFieldAccessor getter,
                    Map<Integer, List<Listener>> paintListeners) {

                final Display display = tree.getDisplay();

                tree.addListener(SWT.EraseItem, new Listener() {
                    @Override
                    public void handleEvent(Event event) {

                        if (event.index != columnIndex) {
                            return;
                        }
                        event.detail &= ~SWT.HOT;

                        if ((event.detail & SWT.SELECTED) != 0) {
                            GC gc = event.gc;
                            Rectangle area = tree.getClientArea();
                            /*
                             * If you wish to paint the selection beyond the end of last column, you must change the
                             * clipping region.
                             */
                            int columnCount = tree.getColumnCount();
                            if (event.index == columnCount - 1 || columnCount == 0) {
                                int width = area.x + area.width - event.x;
                                if (width > 0) {
                                    Region region = new Region();
                                    gc.getClipping(region);
                                    region.add(event.x, event.y, width, event.height);
                                    gc.setClipping(region);
                                    region.dispose();
                                }
                            }
                            gc.setAdvanced(true);
                            if (gc.getAdvanced()) {
                                gc.setAlpha(127);
                            }
                            Rectangle rect = event.getBounds();
                            Color foreground = gc.getForeground();
                            Color background = gc.getBackground();
                            gc.setForeground(display.getSystemColor(systemColourIndex));
                            gc.setBackground(display.getSystemColor(SWT.COLOR_LIST_BACKGROUND));
                            gc.fillGradientRectangle(event.x, rect.y, 500, rect.height, false);

                            gc.setForeground(display.getSystemColor(SWT.COLOR_LIST_SELECTION_TEXT));
                            gc.drawLine(event.x, rect.y, event.x + 20, rect.y + 20);

                            gc.setForeground(foreground); // restore colors for subsequent drawing
                            gc.setBackground(background);
                            event.detail &= ~SWT.SELECTED;
                        }
                    }
                });
            }
        };
    }

    public static CellPainterBuilder textBuilderFor(final int systemColourIndex) {

        return new CellPainterBuilder() {
            @Override
            public void addPainterFor(final Tree tree, final int columnIndex, final RuleFieldAccessor getter,
                    Map<Integer, List<Listener>> paintListeners) {

                // final Display display = tree.getDisplay();

                // tree.addListener(SWT.EraseItem, new Listener() {
                // public void handleEvent(Event event) {
                // if (event.index != columnIndex) return;
                //
                // // GC gc = event.gc;
                // // Rectangle area = new Rectangle(event.x, event.y, event.width, event.height);
                //
                // // gc.fillRectangle(area);
                // }
                //
                // });

                tree.addListener(SWT.PaintItem, new Listener() {
                    @Override
                    public void handleEvent(Event event) {

                        if (event.index != columnIndex) {
                            return;
                        }
                        event.detail &= ~SWT.HOT;

                        // if ((event.detail & SWT.SELECTED) != 0) {
                        GC gc = event.gc;
                        Rectangle area = tree.getClientArea();

                        Rule rule = (Rule) ((TreeItem) event.item).getData();
                        String text = getter.valueFor(rule).toString();

                        int columnCount = tree.getColumnCount();
                        if (event.index == columnCount - 1 || columnCount == 0) {
                            int width = area.x + area.width - event.x;
                            if (width > 0) {
                                Region region = new Region();
                                gc.getClipping(region);
                                region.add(event.x, event.y, width, event.height);
                                gc.setClipping(region);
                                region.dispose();
                            }
                        }
                        // gc.setAdvanced(true);
                        // if (gc.getAdvanced()) gc.setAlpha(127);
                        Rectangle rect = event.getBounds();
                        // Color foreground = gc.getForeground();
                        // Color background = gc.getBackground();
                        // gc.setForeground(display.getSystemColor(systemColourIndex));
                        // gc.setBackground(display.getSystemColor(SWT.COLOR_LIST_BACKGROUND));

                        // gc.setForeground(display.getSystemColor(SWT.COLOR_LIST_SELECTION_TEXT));
                        gc.drawString(text, event.x, rect.y);

                        // gc.setForeground(foreground); // restore colors for subsequent drawing
                        // gc.setBackground(background);
                        // event.detail &= ~SWT.SELECTED;
                        // }
                    }
                });
            }
        };
    }

    public static String asString(List<String> items, String separator) {
        if (items == null || items.isEmpty()) {
            return "";
        }
        if (items.size() == 1) {
            return items.get(0);
        }

        StringBuilder sb = new StringBuilder(items.get(0));
        for (int i = 1; i < items.size(); i++) {
            sb.append(separator).append(items.get(i));
        }
        return sb.toString();
    }

    public static void asString(Object[] items, String separator, StringBuilder target) {
        if (items == null || items.length == 0) {
            return;
        }

        target.append(items[0]);
        for (int i = 1; i < items.length; i++) {
            target.append(separator).append(items[i]);
        }
    }
}
